/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.bytedance.scenedemo.animatiom.sipnner;

import com.bytedance.scene.dialog.bottomsheet.LogUtil;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilityPackage;
import ohos.aafwk.ability.Lifecycle;
import ohos.aafwk.ability.LifecycleStateObserver;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Component;
import ohos.agp.components.Component.BindStateChangedListener;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.StackLayout;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.TextField;
import ohos.agp.utils.Rect;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;

import static com.bytedance.scenedemo.animatiom.sipnner.PopupAnimation.NoAnimation;

/**
 * 弹窗基类
 *
 * @since 2021-06-17
 */
public abstract class BasePopupView extends StackLayout implements
        BindStateChangedListener, Component.TouchEventListener, LifecycleStateObserver {
    private static final int TOUCH_SLOP = 24; // 触发移动事件的最小距离
    private static final int DELAY_TIME = 10;
    private static final int SECOND = 2;
    private static final int TEN = 10;
    private static final int INVALID = -1;
    protected PopupAnimator popupContentAnimator;
    protected BlurAnimator blurAnimator;
    protected boolean isCreated = false;
    private EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());
    private float xx;
    private float yy;
    private Runnable dismissWithRunnable;
    /**
     * PopupInfo
     */
    private PopupInfo popupInfo;
    /**
     * popupStatus
     */
    private PopupStatus popupStatus = PopupStatus.Dismiss;
    /**
     * dialog
     */
    private FullScreenDialog dialog;
    private final Runnable initTask = new Runnable() {
        @Override
        public void run() {
            if (dialog == null) {
                return;
            }
            focusAndProcessBackPress();

            // 由于Attach弹窗有个位置设置过程，需要在位置设置完毕自己开启动画
            if (!(BasePopupView.this instanceof AttachPopupView)) {
                initAnimator(); // 2. 收集动画执行器
                doShowAnimation(); // 3. 执行动画
                doAfterShow();
            }
        }
    };

    private final Runnable attachTask = () -> {
        // 1. add PopupView to its dialog.
        init();
        attachDialog();
    };

    private final Runnable doAfterShowTask = new Runnable() {
        @Override
        public void run() {
            popupStatus = PopupStatus.Show;
            onShow(); // 再次检测移动距离
            if (dialog != null) {
                LogUtil.info("XPopup", "waiting for improvement");
            }
        }
    };

    private final Runnable doAfterDismissTask = new Runnable() {
        @Override
        public void run() {
            if (popupInfo == null) {
                return;
            }
            onDismiss();

            Popup.setLongClickPoint(null);
            if (dismissWithRunnable != null) {
                dismissWithRunnable.run();
                dismissWithRunnable = null; // no cache, avoid some bad edge effect.
            }
            popupStatus = PopupStatus.Dismiss;

            if (popupInfo.isRequestFocus()) {
                // 让根布局拿焦点，避免布局内RecyclerView类似布局获取焦点导致布局滚动
                if (popupInfo.getDecorView() != null) {
                    LogUtil.info("XPopup", "waiting for improvement");
                }
            }
            detachFromHost(); // 移除弹窗，GameOver
        }
    };

    /**
     * 构造方法
     *
     * @param context   Context
     * @param popupInfo PopupInfo
     * @throws IllegalArgumentException
     */
    public BasePopupView(Context context, PopupInfo popupInfo) {
        super(context);
        if (context instanceof AbilityPackage) {
            throw new IllegalArgumentException("XPopup的Context必须是Ability类型！");
        }
        this.popupInfo = popupInfo;

        // 添加Popup窗体内容View
        Component contentView = LayoutScatter.getInstance(context).parse(getPopupLayoutId(), this, false);
        if (popupInfo != null && getMaxWidth() != 0) {
            ComponentContainer.LayoutConfig layoutConfig = contentView.getLayoutConfig();
            layoutConfig.width = getMaxWidth();
            contentView.setLayoutConfig(layoutConfig);
        }
        contentView.setAlpha(0); // 事先隐藏，等测量完毕恢复，避免View影子跳动现象。
        setVisibility(INVISIBLE);
        addComponent(contentView);
        setTouchEventListener(this);
        setBindStateChangedListener(this);
    }

    /**
     * 执行初始化
     */
    protected void init() {
        // 1. 初始化Popup
        if (this instanceof AttachPopupView || this instanceof PartShadowPopupView) {
            initPopupContent();
        } else if (!isCreated) {
            initPopupContent();
        }
        if (!isCreated) {
            isCreated = true;
            onCreate();
        }
        handler.postTask(initTask, DELAY_TIME);
    }

    private void detachFromHost() {
        if (dialog != null) {
            dialog.destroy();
        }
    }

    /**
     * 初始化动画
     */
    protected void initAnimator() {
        // 优先使用自定义的动画器
        if (popupInfo.getCustomAnimator() != null) {
            popupContentAnimator = popupInfo.getCustomAnimator();
            popupContentAnimator.setTargetView(getPopupContentView());
        } else {
            // 根据PopupInfo的popupAnimation字段来生成对应的动画执行器，如果popupAnimation字段为null，则返回null
            if (popupContentAnimator == null) {
                popupContentAnimator = getPopupAnimator();
            }
        }

        if (popupInfo.getHasBlurBg()) {
            blurAnimator = new BlurAnimator(this);
            blurAnimator.setShadowBg(popupInfo.getHasBlurBg());
            blurAnimator.initAnimator();
        }
        if (popupContentAnimator != null) {
            popupContentAnimator.initAnimator();
        }
    }

    /**
     * 展示
     *
     * @return BasePopupView
     */
    public BasePopupView show() {
        if (popupStatus == PopupStatus.Showing) {
            return this;
        }
        popupStatus = PopupStatus.Showing;
        if (dialog != null && dialog.isShowing()) {
            return BasePopupView.this;
        }
        handler.postTask(attachTask);
        return this;
    }

    private void attachDialog() {
        if (dialog == null) {
            dialog = new FullScreenDialog(getContext())
                    .setContent(this);
        }
        if (getContext() instanceof Ability) {
            ((Ability) getContext()).getLifecycle().addObserver(this);
        }
        dialog.show();
        if (popupInfo == null) {
            throw new IllegalArgumentException("如果弹窗对象是复用的，则不要设置isDestroyOnDismiss(true)");
        }
    }

    /**
     * doAfterShow
     */
    protected void doAfterShow() {
        handler.removeTask(doAfterShowTask);
        handler.postTask(doAfterShowTask, getAnimationDuration());
    }

    /**
     * focusAndProcessBackPress
     */
    public void focusAndProcessBackPress() {
        if (popupInfo != null && popupInfo.isRequestFocus()) {
            setTouchFocusable(true);
            requestFocus();

            // let all EditText can process back pressed.
            ArrayList<TextField> list = new ArrayList<>();
            PopupUtils.findAllEditText(list, (ComponentContainer) getPopupContentView());
        }
    }

    /**
     * 获取弹窗外层布局资源id
     *
     * @return 弹窗外层布局资源id
     */
    protected abstract int getPopupLayoutId();

    /**
     * 如果你自己继承BasePopupView来做，这个不用实现
     *
     * @return 弹窗内层布局资源id
     */
    protected int getImplLayoutId() {
        return INVALID;
    }

    /**
     * 获取PopupAnimator，用于每种类型的PopupView自定义自己的动画器
     *
     * @return 动画执行器
     */
    protected PopupAnimator getPopupAnimator() {
        PopupAnimator popupAnimator = null;
        return popupAnimator;
    }

    /**
     * 请使用onCreate，主要给弹窗内部用，不要去重写。
     */
    protected void initPopupContent() {
    }

    /**
     * do init.
     */
    protected void onCreate() {
    }

    /**
     * applyLightTheme
     */
    protected void applyLightTheme() {
    }

    /**
     * 执行显示动画：动画由2部分组成，一个是背景渐变动画，一个是Content的动画；
     * 背景动画由父类实现，Content由子类实现
     */
    protected void doShowAnimation() {
        getPopupContentView().setAlpha(1f);
        setVisibility(VISIBLE);
        if (popupInfo.getHasBlurBg() && blurAnimator != null) {
            blurAnimator.animateShow();
        }
        if (popupContentAnimator != null) {
            popupContentAnimator.animateShow();
        }
    }

    /**
     * 执行消失动画：动画由2部分组成，一个是背景渐变动画，一个是Content的动画；
     * 背景动画由父类实现，Content由子类实现
     */
    protected void doDismissAnimation() {
        if (popupInfo.getHasBlurBg() && blurAnimator != null) {
            blurAnimator.animateDismiss();
        }
        if (popupContentAnimator != null) {
            popupContentAnimator.animateDismiss();
        }
    }

    /**
     * 获取内容View，本质上PopupView显示的内容都在这个View内部。
     * 而且我们对PopupView执行的动画，也是对它执行的动画
     *
     * @return 获取弹窗的内容控件
     */
    public Component getPopupContentView() {
        return getComponentAt(0);
    }

    public Component getPopupImplView() {
        return ((ComponentContainer) getPopupContentView()).getComponentAt(0);
    }

    public int getAnimationDuration() {
        return popupInfo.getPopupAnimation() == NoAnimation ? TEN : Popup.getAnimationDuration() + TEN;
    }

    /**
     * 弹窗的最大宽度，用来限制弹窗的最大宽度
     * 返回0表示不限制，默认为0
     *
     * @return 弹窗的最大宽度
     */
    protected int getMaxWidth() {
        return popupInfo.getMaxWidth();
    }

    /**
     * 弹窗的最大高度，用来限制弹窗的最大高度
     * 返回0表示不限制，默认为0
     *
     * @return 弹窗的最大高度
     */
    protected int getMaxHeight() {
        return popupInfo.getMaxHeight();
    }

    /**
     * 弹窗的宽度，用来动态设定当前弹窗的宽度，受getMaxWidth()限制
     * 返回0表示不设置，默认为0
     *
     * @return 弹窗的宽度
     */
    protected int getPopupWidth() {
        return popupInfo.getPopupWidth();
    }

    /**
     * 弹窗的高度，用来动态设定当前弹窗的高度，受getMaxHeight()限制
     * 返回0表示不设置，默认为0
     *
     * @return 弹窗的高度
     */
    protected int getPopupHeight() {
        return popupInfo.getPopupHeight();
    }

    /**
     * 消失
     */
    public void dismiss() {
        handler.removeTask(attachTask);
        handler.removeTask(initTask);
        if (popupStatus == PopupStatus.Dismissing || popupStatus == PopupStatus.Dismiss) {
            return;
        }
        popupStatus = PopupStatus.Dismissing;
        clearFocus();
        beforeDismiss();
        doDismissAnimation();
        doAfterDismiss();
    }

    /**
     * doAfterDismiss
     */
    protected void doAfterDismiss() {
        handler.removeTask(doAfterDismissTask);
        handler.postTask(doAfterDismissTask, getAnimationDuration());
    }

    public boolean isShow() {
        return popupStatus != PopupStatus.Dismiss;
    }

    /**
     * 消失动画执行完毕后执行
     */
    protected void onDismiss() {
    }

    /**
     * 开始消失的时候执行一次
     */
    protected void beforeDismiss() {
    }

    /**
     * 显示动画执行完毕后执行
     */
    protected void onShow() {
    }

    @Override
    public void onStateChanged(Lifecycle.Event event, Intent intent) {
        onDestroy();
    }

    /**
     * onDestroy
     */
    public void onDestroy() {
        destroy();
    }

    /**
     * destroy
     */
    public void destroy() {
        detachFromHost();
        onComponentUnboundFromWindow(null);
        if (popupInfo != null) {
            popupInfo.setAtView(null);
            popupInfo.setWatchView(null);
            popupInfo.setDecorView(null);
            dialog = null;
            if (popupInfo.isDestroyOnDismiss()) {
                popupInfo = null;
            }
        }
        if (getContext() != null && getContext() instanceof Ability) {
            ((Ability) getContext()).getLifecycle().removeObserver(this);
        }
    }

    @Override
    public void onComponentUnboundFromWindow(Component component) {
        handler.removeAllEvent();
        if (popupInfo != null) {
            if (popupInfo.isDestroyOnDismiss()) { // 如果开启isDestroyOnDismiss，强制释放资源
                popupInfo.setAtView(null);
                popupInfo.setWatchView(null);
                popupInfo.setDecorView(null);
                popupInfo = null;
                dialog = null;
                if (getContext() instanceof Ability) {
                    ((Ability) getContext()).getLifecycle().removeObserver(this);
                }
                if (blurAnimator != null && blurAnimator.getDecorBitmap() != null
                        && !blurAnimator.getDecorBitmap().isReleased()) {
                    blurAnimator.getDecorBitmap().release();
                    blurAnimator.setDecorBitmap(null);
                }
            }
        }
        popupStatus = PopupStatus.Dismiss;
    }

    @Override
    public void onComponentBoundToWindow(Component component) {
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        // 如果自己接触到了点击，并且不在PopupContentView范围内点击，则进行判断是否是点击事件,如果是，则dismiss
        Rect rect = new Rect();
        Rect rect2 = new Rect();
        getGlobalVisibleRect(getPopupImplView(), rect);

        if (!PopupUtils.isInRect(PopupUtils.getX(event), PopupUtils.getY(event), rect)) {
            switch (event.getAction()) {
                case TouchEvent.PRIMARY_POINT_DOWN:
                    xx = PopupUtils.getRawX(event);
                    yy = PopupUtils.getRawY(event);
                    if (dialog != null && popupInfo != null && popupInfo.isClickThrough()) {
                        dialog.passClick(event);
                    }
                    break;
                case TouchEvent.PRIMARY_POINT_UP:
                case TouchEvent.CANCEL:
                    float dx = PopupUtils.getRawX(event) - xx;
                    float dy = PopupUtils.getRawY(event) - yy;
                    float distance = (float) Math.sqrt(Math.pow(dx, SECOND) + Math.pow(dy, SECOND));
                    if (distance < TOUCH_SLOP && popupInfo.getDismissOnTouchOutside()) {
                        dismiss();
                        getGlobalVisibleRect(getPopupImplView(), rect2);
                        clickDialog(event, rect2);
                    }
                    xx = 0;
                    yy = 0;
                    break;
                default:
                    break;
            }
        }
        return true;
    }

    private void clickDialog(TouchEvent event, Rect rect2) {
        if (!PopupUtils.isInRect(PopupUtils.getX(event), PopupUtils.getY(event), rect2)) {
            if (dialog != null && popupInfo != null && popupInfo.isClickThrough()) {
                dialog.passClick(event);
            }
        }
    }

    /**
     * 获取控件的显示区域
     *
     * @param component 控件
     * @param rect      用于存储显示区域的Rect
     * @return 存储了控件显示区域的Rect
     */
    private Rect getGlobalVisibleRect(Component component, Rect rect) {
        int[] locationOnScreen = component.getLocationOnScreen();
        rect.left = locationOnScreen[0];
        rect.right = rect.left + component.getWidth();
        rect.top = locationOnScreen[1];
        rect.bottom = rect.top + component.getHeight();
        return rect;
    }

    public void setPopupInfo(PopupInfo popupInfo) {
        this.popupInfo = popupInfo;
    }

    public PopupInfo getPopupInfo() {
        return popupInfo;
    }

    public void setPopupStatus(PopupStatus popupStatus) {
        this.popupStatus = popupStatus;
    }

    public PopupStatus getPopupStatus() {
        return popupStatus;
    }

    public FullScreenDialog getDialog() {
        return dialog;
    }

    public void setDialog(FullScreenDialog dialog) {
        this.dialog = dialog;
    }
}
